package com.cls.common.interceptor;

import cn.hutool.json.JSONUtil;
import com.cls.common.annotation.EncryptParam;
import com.cls.common.utils.CryptPojoUtils;
import com.cls.common.annotation.EncryptField;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author weimaomao
 * @date 2021/3/18
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class EncryptDecryptFieldInterceptor implements Interceptor {
    private final Logger logger = LoggerFactory.getLogger(EncryptDecryptFieldInterceptor.class);
    static int MAPPED_STATEMENT_INDEX = 0;
    static int PARAMETER_INDEX = 1;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("EncryptDecryptFieldInterceptor.intercept开始执行==> ");
        MappedStatement statement = (MappedStatement) invocation.getArgs()[MAPPED_STATEMENT_INDEX];
        Object parameter = invocation.getArgs()[PARAMETER_INDEX];
        logger.info(statement.getId() + "未加密参数串:" + JSONUtil.toJsonStr(parameter));
        //执行参数加密
        parameter = encryptParam(parameter, invocation);
        logger.info(statement.getId() + "加密后参数:" + JSONUtil.toJsonStr(parameter));
        invocation.getArgs()[PARAMETER_INDEX] = parameter;

        //获取到返回结果
        Object returnValue = invocation.proceed();
        logger.info(statement.getId() + "未解密结果集:" + JSONUtil.toJsonStr(returnValue));
        //对返回结果进行加密处理
        returnValue = decryptReslut(returnValue);
        logger.info(statement.getId() + "解密后结果集:" + JSONUtil.toJsonStr(returnValue));
        logger.info("EncryptDecryptFieldInterceptor.intercept执行结束==> ");
        return returnValue;

    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }


    /**
     * 针对不同的参数类型进行加密
     *
     * @param parameter  参数列表
     * @param invocation invocation
     * @return 返回加密后的数据
     */
    public Object encryptParam(Object parameter, Invocation invocation) {
        MappedStatement statement = (MappedStatement) invocation.getArgs()[MAPPED_STATEMENT_INDEX];
        //针对string的参数加密
        if (parameter instanceof String) {
            if (isEncryptStr(statement)) {
                parameter = CryptPojoUtils.encryptStr(parameter);
            }
        }//针对Map参数进行加密
        else if (parameter instanceof Map) {
            List<String> fields = getEncryFieldList(statement);
            if (fields != null && fields.size() > 0) {
                parameter = CryptPojoUtils.getEncryptMapValue(parameter, fields);
            }//判断是否是对象类型，对对象类型的数据进行处理,因为map中的数据是引用传递，加密一次就可以了
            else {
                Map parameterMap = (Map) parameter;
                Object param1 = parameterMap.get("param1");
                CryptPojoUtils.encrypt(param1);
            }
        }
        //针对对象的参数加密
        else {
            return CryptPojoUtils.encrypt(parameter);
        }
        return parameter;
    }


    /**
     * 获取参数map中需要加密字段
     *
     * @param statement
     * @return List<String>
     * @throws
     */
    private List<String> getEncryFieldList(MappedStatement statement) {
        List<String> strList = new ArrayList<>();
        Method method = getDaoTargetMethod(statement);
        if (method == null) {
            return null;
        }
        method.setAccessible(true);
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations != null && parameterAnnotations.length > 0) {
            for (Annotation[] parameterAnnotation : parameterAnnotations) {
                for (Annotation annotation : parameterAnnotation) {
                    if (annotation instanceof EncryptParam) {
                        String encryString = ((EncryptParam) annotation).value();
                        if (!"".equals(encryString)) {
                            strList.add(((EncryptParam) annotation).value());
                        }
                    }
                }
            }
        }
        return strList;
    }

    /**
     * 判断字符串是否需要加密
     *
     * @param mappedStatement mapper
     * @return 返回是否要加密
     */
    private boolean isEncryptStr(MappedStatement mappedStatement) {
        try {
            Method m = getDaoTargetMethod(mappedStatement);
            m.setAccessible(true);
            Annotation[][] parameterAnnotations = m.getParameterAnnotations();
            if (parameterAnnotations != null && parameterAnnotations.length > 0) {
                for (Annotation[] parameterAnnotation : parameterAnnotations) {
                    for (Annotation annotation : parameterAnnotation) {
                        if (annotation instanceof EncryptField) {
                            return true;
                        }
                    }
                }
            }
        } catch (SecurityException e) {
            e.printStackTrace();
            logger.info("EncryptDaoInterceptor.isEncryptStr异常：==> " + e.getMessage());
        }
        return false;
    }

    /**
     * 获取Dao层接口方法
     *
     * @param mappedStatement mapper
     * @return 接口方法
     */
    private Method getDaoTargetMethod(MappedStatement mappedStatement) {
        Method method = null;
        try {
            String namespace = mappedStatement.getId();
            String className = namespace.substring(0, namespace.lastIndexOf("."));
            String methedName = namespace.substring(namespace.lastIndexOf(".") + 1);
            Method[] ms = Class.forName(className).getMethods();
            for (Method m : ms) {
                if (m.getName().equals(methedName)) {
                    method = m;
                    break;
                }
            }
        } catch (SecurityException e) {
            e.printStackTrace();
            logger.info("EncryptDecryptFieldInterceptor.getDaoTargetMethod方法异常==> " + e.getMessage());
            return null;

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.info("EncryptDecryptFieldInterceptor.getDaoTargetMethod方法异常==> " + e.getMessage());
            return null;
        }
        return method;
    }


    /**
     * 解密结果集
     *
     * @param returnValue 返回的数据
     * @return 解密结果
     */
    public Object decryptReslut(Object returnValue) {
        if (returnValue != null) {
            if (returnValue instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) returnValue;
                List<Object> newList = new ArrayList<>();
                if (1 <= list.size()) {
                    for (Object object : list) {
                        Object obj = CryptPojoUtils.decrypt(object);
                        newList.add(obj);
                    }
                    returnValue = newList;
                }
            } else {
                return CryptPojoUtils.decrypt(returnValue);
            }
        }
        return returnValue;
    }

}
